/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author Alexei Y. Zakharov
 */

package org.apache.harmony.jndi.provider.dns;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Vector;

/**
 * A cache for received resource records. Common for all active resolvers.
 * 
 * TODO handling of records with TTL set to 0; should not be cached.
 */
class ResolverCache {

	/**
	 * Represents SLIST cache entry.
	 */
	static class CacheEntry {

		private final ResourceRecord rr;

		private final long bestBefore;

		/**
		 * Constructs new cache entry.
		 * 
		 * @param rr
		 *            Resource Record
		 * @param bestBefore
		 *            best before (time in millis)
		 */
		public CacheEntry(ResourceRecord rr, long bestBefore) {
			this.rr = rr;
			this.bestBefore = bestBefore;
		}

		/**
		 * @return Returns the bestBefore.
		 */
		public long getBestBefore() {
			return bestBefore;
		}

		/**
		 * @return Returns the Resource Record.
		 */
		public ResourceRecord getRR() {
			return rr;
		}
	}

	/** keys - zone & host names; values - vectors with RRs */
	HashMap<String, Vector<CacheEntry>> names = new HashMap<String, Vector<CacheEntry>>();

	private static ResolverCache instance = null;

	/**
	 * <code>ResolverCache</code> is a singleton class.
	 * 
	 * @return active instance of <code>ResolverCache</code>
	 */
	static ResolverCache getInstance() {
		if (instance == null) {
			instance = new ResolverCache();
		}
		return instance;
	}

	/**
	 * Since <code>ResolverCache</code> is singleton class its constructor
	 * should be hidden.
	 */
	private ResolverCache() {
		names = new HashMap<String, Vector<CacheEntry>>();
	}

	/**
	 * Removes all cached entries.
	 */
	synchronized void clear() {
		names = new HashMap<String, Vector<CacheEntry>>();
	}

	/**
	 * Looks through the cache and returns all suitable resource records
	 * 
	 * @param question
	 *            a question record that determines which records we want to get
	 *            from the cache
	 * @return Enumeration of found Resource Records.
	 */
	synchronized Enumeration<ResourceRecord> get(QuestionRecord question) {
		final String name = question.getQName().toLowerCase();
		final Vector<CacheEntry> vect = names.get(name);
		final int qClass = question.getQClass();
		final int qType = question.getQType();
		final Vector<ResourceRecord> resVect = new Vector<ResourceRecord>();

		if (vect != null) {
			for (int i = 0; i < vect.size(); i++) {
				final CacheEntry curEntry = vect.elementAt(i);
				final ResourceRecord curRR = curEntry.getRR();

				if (curEntry.getBestBefore() < System.currentTimeMillis()) {
					// the record is out of date
					vect.removeElementAt(i--);
					continue;
				}
				if (qClass == ProviderConstants.ANY_QCLASS
						|| qClass != curRR.getRRClass()) {
					continue;
				}
				if (qType == ProviderConstants.ANY_QTYPE
						|| qType != curRR.getRRType()) {
					continue;
				}
				resVect.addElement(curRR);
			}
		}
		return resVect.elements();
	}

	/**
	 * Puts element into the cache. Doesn't put records with zero TTLs. Doesn't
	 * put records with bad TTLs.
	 * 
	 * @param record
	 *            a resource record to insert
	 */
	synchronized void put(ResourceRecord record) {
		final String name = record.getName().toLowerCase();
		Vector<CacheEntry> vect = names.get(name);
		final long curTime = System.currentTimeMillis();
		CacheEntry entry = null;

		if (vect == null) {
			vect = new Vector<CacheEntry>();
			names.put(name, vect);
		}
		// TTL should be between 0 and 2^31; if greater - should be set to 0
		// See RFC 2181 point 8
		if (record.getTtl() >> 31 != 0) {
			record.setTtl(0);
		}
		// skip records with wildcards in names or with zero TTL
		if (record.getTtl() > 0 && (record.getName().indexOf('*') == -1)) {
			entry = new CacheEntry(record, curTime + record.getTtl());
			// remove old occurrence if any
			for (int i = 0; i < vect.size(); i++) {
				final CacheEntry exEntry = vect.elementAt(i);
				final ResourceRecord exRec = exEntry.rr;

				if (ProviderMgr
						.namesAreEqual(record.getName(), exRec.getName())
						&& record.getRRClass() == exRec.getRRClass()
						&& record.getRRType() == exRec.getRRType()) {
					if (record.getRData() != null && exRec.getRData() != null
							&& record.getRData().equals(exRec.getRData())) {
						vect.remove(i);
						break;
					}
				}
			}
			vect.addElement(entry);
		}
	}

}
